/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.projects.csv.business.manager.impl;

import java.util.Collection;
import java.util.Map;

import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.persistence.criteria.Criteria;
import com.genia.toolbox.persistence.criteria.PropertyField;
import com.genia.toolbox.persistence.criteria.SimplePropertyField;
import com.genia.toolbox.persistence.dao.AbstractDAO;
import com.genia.toolbox.persistence.exception.PersistenceException;
import com.genia.toolbox.projects.csv.bean.Binder;
import com.genia.toolbox.projects.csv.bean.EntityBinder;
import com.genia.toolbox.projects.csv.bean.FieldBinder;
import com.genia.toolbox.projects.csv.bean.FieldDescriptor;
import com.genia.toolbox.projects.csv.business.manager.FillCriteriaBinderVisitor;
import com.genia.toolbox.projects.csv.enumeration.FieldType;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.I18nDynamicValue;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.Language;
import com.genia.toolbox.projects.toolbox_basics_project.business.dao.I18nSearchHelperDao;
import com.genia.toolbox.projects.toolbox_basics_project.spring.manager.LanguageManager;

/**
 * implementation of {@link FillCriteriaBinderVisitor}.
 */
public class FillCriteriaBinderVisitorImpl
    extends AbstractDAO
    implements FillCriteriaBinderVisitor
{
  /**
   * the current values of the entities.
   */
  private final ThreadLocal<Map<String, Object>> entitiesValues = new ThreadLocal<Map<String, Object>>();

  /**
   * the values of the field of the csv.
   */
  private final ThreadLocal<Map<String, Object>> fieldsValues = new ThreadLocal<Map<String, Object>>();

  /**
   * the criteria to fill.
   */
  private final ThreadLocal<Criteria<?>> criteria = new ThreadLocal<Criteria<?>>();

  /**
   * the {@link LanguageManager} to use.
   */
  private LanguageManager languageManager;

  /**
   * the {@link I18nSearchHelperDao} to use.
   */
  private I18nSearchHelperDao i18nSearchHelperDao;



  /**
   * clear the setted values to prevent memory leak.
   */
  public void clearVisitor()
  {
    entitiesValues.remove();
    fieldsValues.remove();
    criteria.remove();
  }



  /**
   * the actual visit method.
   * 
   * @param binder
   *          the binder to visit
   * @param bindedObjectValue
   *          the value of the object to bind with
   * @throws PersistenceException
   *           if an error occured
   */
  @SuppressWarnings("unchecked")
  public void visitBinder(Binder binder, Object bindedObjectValue)
      throws PersistenceException
  {
    Class<?> bindedObjectClass = binder.getBindedObjectClass();

    final PropertyField computePropertyField = computePropertyField(binder);
    final Criteria<?> criteria = this.criteria.get();
    if (Map.class.isAssignableFrom(bindedObjectClass) || Collection.class.isAssignableFrom(bindedObjectClass)) {
      criteria.addRestriction(isIn(getConstantField(bindedObjectValue), elements(getPropertyField(computePropertyField))));
    }
    else {
      if (I18nDynamicValue.class.isAssignableFrom(bindedObjectClass)) {
        final Language language = getLanguageManager().getLanguage(binder.getEntityDescriptor().getFolderDescriptor().getMappingDescriptor().getMainLocale());
        getI18nSearchHelperDao().addI18nDynamicStringExactRestriction(language, criteria, criteria.getMainType(), computePropertyField, (String) bindedObjectValue);
      }
      else {
        if (bindedObjectValue == null) {
          criteria.addRestriction(isNull(getPropertyField(computePropertyField)));
        }
        else {
          criteria.addRestriction(equals(getPropertyField(computePropertyField), getConstantField(bindedObjectValue)));
        }
      }
    }

  }



  /**
   * visit method for {@link EntityBinder}.
   * 
   * @param binder
   *          the binder to visit
   * @throws BundledException
   *           if an error occured
   * @see com.genia.toolbox.projects.csv.bean.BinderVisitor#visitEntityBinder(EntityBinder)
   */
  public void visitEntityBinder(EntityBinder binder)
      throws BundledException
  {
    visitBinder(binder, entitiesValues.get().get(binder.getEntityName()));
  }



  /**
   * compute the {@link PropertyField} to access the property given by the
   * {@link Binder}.
   * 
   * @param binder
   *          the {@link Binder} to compute the {@link PropertyField} for
   * @return the {@link PropertyField} to access the property given by the
   *         {@link Binder}
   */
  private SimplePropertyField<?> computePropertyField(Binder binder)
  {
    return PropertyField.create(Object.class, binder.getMappedPropertyName());
  }



  /**
   * visit method for {@link FieldBinder}.
   * 
   * @param binder
   *          the binder to visit
   * @throws BundledException
   *           if an error occured
   * @see com.genia.toolbox.projects.csv.bean.BinderVisitor#visitFieldBinder(FieldBinder)
   */
  public void visitFieldBinder(FieldBinder binder)
      throws BundledException
  {
    FieldDescriptor fieldDescriptor = binder.getEntityDescriptor().getFolderDescriptor().getField(binder.getFieldName());
    Object fieldValue = fieldsValues.get().get(binder.getFieldName());
    if ((fieldDescriptor.getFieldType() == FieldType.I18N || fieldDescriptor.getFieldType() == FieldType.I18N_BIND) && fieldValue != null) {
      fieldValue = ((Map<?, ?>) fieldValue).get(binder.getEntityDescriptor().getFolderDescriptor().getMappingDescriptor().getMainLocale());
    }
    visitBinder(binder, fieldValue);
  }



  /**
   * getter for the languageManager property.
   * 
   * @return the languageManager
   */
  public LanguageManager getLanguageManager()
  {
    return languageManager;
  }



  /**
   * setter for the languageManager property.
   * 
   * @param languageManager
   *          the languageManager to set
   */
  public void setLanguageManager(LanguageManager languageManager)
  {
    this.languageManager = languageManager;
  }



  /**
   * getter for the i18nSearchHelperDao property.
   * 
   * @return the i18nSearchHelperDao
   */
  public I18nSearchHelperDao getI18nSearchHelperDao()
  {
    return i18nSearchHelperDao;
  }



  /**
   * setter for the i18nSearchHelperDao property.
   * 
   * @param searchHelperDao
   *          the i18nSearchHelperDao to set
   */
  public void setI18nSearchHelperDao(I18nSearchHelperDao searchHelperDao)
  {
    i18nSearchHelperDao = searchHelperDao;
  }



  /**
   * setter for the criteria property.
   * 
   * @param criteria
   *          the criteria to set
   */
  public void setCriteria(Criteria<?> criteria)
  {
    this.criteria.set(criteria);
  }



  /**
   * setter for the entitiesValues property.
   * 
   * @param entitiesValues
   *          the entitiesValues to set
   */
  public void setEntitiesValues(Map<String, Object> entitiesValues)
  {
    this.entitiesValues.set(entitiesValues);
  }



  /**
   * setter for the fieldsValues property.
   * 
   * @param fieldsValues
   *          the fieldsValues to set
   */
  public void setFieldsValues(Map<String, Object> fieldsValues)
  {
    this.fieldsValues.set(fieldsValues);
  }

}